// Copyright 2017 openconfigd project.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ntp

import (
	"encoding/json"
	"fmt"
	"html/template"
	"os"
	"os/exec"
	"sync"

	"github.com/coreswitch/log"
	"github.com/coreswitch/process"
	"github.com/mitchellh/mapstructure"
)

type Config struct {
	Ntp
	process *process.Process
}

var configMap struct {
	config map[string]*Config
	mutex  sync.RWMutex
}

var configTemplate = `# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help
# This file is generated by openconfigd.

driftfile /var/lib/ntp/ntp.drift

# Enable this if you want statistics to be logged.
#statsdir /var/log/ntpstats/

statistics loopstats peerstats clockstats
filegen loopstats file loopstats type day enable
filegen peerstats file peerstats type day enable
filegen clockstats file clockstats type day enable

# Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for
# details.  The web page <http://support.ntp.org/bin/view/Support/AccessRestrictions>
# might also be helpful.
#
# Note that "restrict" applies to both servers and clients, so a configuration
# that might be intended to block requests from certain clients could also end
# up blocking replies from your own upstream servers.

# By default, exchange time with everybody, but don't allow configuration.
restrict -4 default kod notrap nomodify nopeer noquery limited
restrict -6 default kod notrap nomodify nopeer noquery limited

# Local users may interrogate the ntp server more closely.
restrict 127.0.0.1
restrict ::1

# Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board
# on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for
# more information.
{{range $_, $v := .PoolList}}{{pool2Config $v}}{{end}}
{{range $_, $v := .ServerList}}{{server2Config $v}}{{end}}
`

func UnmarshalJSON(str string) (*Ntp, error) {
	var jsonIntf interface{}
	err := json.Unmarshal([]byte(str), &jsonIntf)
	if err != nil {
		log.Error(err)
		return nil, err
	}
	config := &Ntp{}
	err = mapstructure.Decode(jsonIntf, &config)
	if err != nil {
		log.Error(err)
		return nil, err
	}
	return config, nil
}

func ExtractVRF(path []string) (string, error) {
	if len(path) == 2 {
		if path[0] == "system" && path[1] == "ntp" {
			return "", nil
		}
	}
	if len(path) == 4 {
		if path[0] == "vrf" && path[1] == "name" && path[3] == "ntp" {
			return path[2], nil
		}
	}
	err := fmt.Errorf("NTP config path error: %s", path)
	log.Error(err)
	return "", err
}

func pool2Config(p *Pool) string {
	str := fmt.Sprintf("pool %s iburst", p.Name)
	if p.Dynamic {
		str += " dynamic"
	}
	if p.Noselect {
		str += " noselect"
	}
	if p.Preempt {
		str += " preempt"
	}
	if p.Prefer {
		str += " prefer"
	}
	str += "\n"
	return str
}

func server2Config(s *Server) string {
	str := fmt.Sprintf("server %s iburst", s.Name)
	if s.Dynamic {
		str += " dynamic"
	}
	if s.Noselect {
		str += " noselect"
	}
	if s.Preempt {
		str += " preempt"
	}
	if s.Prefer {
		str += " prefer"
	}
	str += "\n"
	return str
}

func Update(vrfName string, ntp *Ntp) error {
	configMap.mutex.Lock()
	defer configMap.mutex.Unlock()

	if vrfName != "" {
		err := fmt.Errorf("We don't support NTP on VRF: %s", vrfName)
		log.Debugf(err.Error())
		return err
	}

	configFileName := "/etc/ntp.conf"
	fd, err := os.Create(configFileName)
	if err != nil {
		log.Error(err)
		return err
	}

	tmpl := template.Must(template.New("template").Funcs(template.FuncMap{
		"pool2Config":   pool2Config,
		"server2Config": server2Config,
	}).Parse(configTemplate))

	err = tmpl.Execute(fd, ntp)
	if err != nil {
		log.Error(err)
	}

	err = fd.Close()
	if err != nil {
		log.Error(err)
		return err
	}

	// Process restart
	if vrfName == "" {
		err = exec.Command("systemctl", "restart", "ntp").Run()
		if err != nil {
			log.Error(err)
			return err
		}
	}

	return nil
}

func Configure(path []string, str string) error {
	Start()

	ntp, err := UnmarshalJSON(str)
	if err != nil {
		return err
	}

	vrfName, err := ExtractVRF(path)
	if err != nil {
		return err
	}
	fmt.Println(vrfName)

	err = Update(vrfName, ntp)
	if err != nil {
		return err
	}

	return nil
}

func Start() {
	log.SetLevel("debug")
	log.SetJSONFormatter()
}

func Stop() {
	fmt.Println("ntp stop")
}
